Las pruebas son la parte más importante del desarrollo de software. No importa cuan bonita, rápida o tecnológicamente avanzada sea tu aplicación, si falla, darás una impresión muy pobre.
Hacer pruebas manuales, es decir, abrir el navegador y ejecutar la aplicación exactamente como lo haría un usuario final, no es viable; porque el problema real no está en el código que acabas de escribir, sino en el código que ya estaba ahí. Normalmente pruebas el código que acabas de escribir, pero no pruebas todo el código que ya existe en tu aplicación. Y sabes muy bien que cuando tocas cualquier parte de tu aplicación puedes romper cualquier otra parte inadvertidamente.
Necesitas poder hacer cualquier cambio en tu código con la tranquilidad de que no vas a romper tu aplicación. Una forma de conseguirlo, es usando pruebas automáticas. Vamos a hacer pruebas automáticas usando JUnit.
JUnit
JUnit es una herramienta muy popular para hacer pruebas automáticas. Esta herramienta está integrada con Eclipse, por tanto no necesitas descargarla para poder usarla. OpenXava extiende las capacidades de JUnit para permitir probar un módulo de OpenXava exactamente de la misma forma que lo haría un usuario final. De hecho, OpenXava usa HtmlUnit, un software que simula un navegador real (incluyendo JavaScript) desde Java. Todo está disponible desde la clase de OpenXava ModuleTestBase, que te permite automatizar las pruebas que tú harías a mano usando un navegador de verdad de una forma simple.
La mejor manera de entender como funcionan las pruebas en OpenXava es verlo en acción.
ModuleTestBase para probar módulos
Para crear una prueba para un módulo de OpenXava extendemos de la clase ModuleTestBase del paquete org.openxava.tests. Esta clase te permite conectar con un módulo OpenXava como un navegador real, y tiene muchos métodos útiles para probar tu módulo. Creemos la prueba para tu módulo Cliente.
El código para la prueba
Crea un paquete nuevo llamado org.openxava.facturacion.pruebas y dentro de él una nueva clase llamada PruebaCliente con el siguiente código:
packageorg.openxava.facturacion.pruebas;importorg.openxava.tests.*;publicclass PruebaCliente extends ModuleTestBase {// Ha de extender de ModuleTestBasepublic PruebaCliente(String nombrePrueba){super(nombrePrueba, "Facturacion", // Indicamos el nombre de aplicación (Facturacion)"Cliente");// y nombre de módulo (Cliente)}// Los métodos de prueba han de empezar por 'test'publicvoid testCrearLeerActualizarBorrar()throwsException{
login("admin", "admin");// Identificación de usuario para acceder al módulo// Crear
execute("CRUD.new");// Pulsa el botón 'Nuevo'
setValue("numero", "77");// Teclea 77 como valor para el campo 'numero'
setValue("nombre", "Cliente JUNIT");// Pone valor en el campo 'nombre'
setValue("direccion.viaPublica", "Calle JUNIT");// Fíjate en la notación del punto// para acceder al miembro de la referencia
setValue("direccion.codigoPostal", "77555");// Etc
setValue("direccion.municipio", "La ciudad JUNIT");// Etc
setValue("direccion.provincia", "La provincia JUNIT");// Etc
execute("CRUD.save");// Pulsa el botón 'Grabar'
assertNoErrors();// Verifica que la aplicación no muestra errores
assertValue("numero", "");// Verifica que el campo 'numero' está vacío
assertValue("nombre", "");// Verifica que el campo 'nombre' está vacío
assertValue("direccion.viaPublica", "");// Etc
assertValue("direccion.codigoPostal", "");// Etc
assertValue("direccion.municipio", "");// Etc
assertValue("direccion.provincia", "");// Etc// Leer
setValue("numero", "77");// Pone 77 como valor para el campo 'numero'
execute("CRUD.refresh");// Pulsa el botón 'Refrescar'
assertValue("numero", "77");// Verifica que el campo 'numero' tiene un 77
assertValue("nombre", "Cliente JUNIT");// y 'nombre' tiene 'Cliente JUNIT'
assertValue("direccion.viaPublica", "Calle JUNIT");// Etc
assertValue("direccion.codigoPostal", "77555");// Etc
assertValue("direccion.municipio", "La ciudad JUNIT");// Etc
assertValue("direccion.provincia", "La provincia JUNIT");// Etc// Actualizar
setValue("nombre", "Cliente JUNIT MODIFICADO");// Cambia el valor del campo 'nombre'
execute("CRUD.save");// Pulsa el botón 'Grabar'
assertNoErrors();// Verifica que la aplicación no muestra errores
assertValue("numero", "");// Verifica que el campo 'numero' está vacío
assertValue("nombre", "");// Verifica que el campo 'nombre' está vacío// Verifica si se ha modificado
setValue("numero", "77");// Pone 77 como valor para el campo 'numero'
execute("CRUD.refresh");// Pulsa en el botón 'Refrescar'
assertValue("numero", "77");// Verifica que el campo 'numero' tiene un 77
assertValue("nombre", "Cliente JUNIT MODIFICADO");// y 'nombre' tiene// 'Cliente JUNIT MODIFICADO'// Borrar
execute("CRUD.delete");// Pulsa en el botón 'Borrar'
assertMessage("Cliente borrado satisfactoriamente");// Verifica que el mensaje// 'Cliente borrado satisfactoriamente' se muestra al usuario}}
Esta prueba crea un nuevo cliente, lo busca, lo modifica y al final lo borra. Aquí ves como puedes usar métodos como execute() o setValue() para simular las acciones del usuario, y métodos como assertValue(), assertNoErrors() o assertMessage() para verificar el estado de la interfaz de usuario. Tu prueba actúa como las manos y los ojos del usuario:
En execute() tienes que especificar el nombre calificado de la acción, esto quiere decir NombreControlador.nombreAccion. ¿Cómo puede saber el nombre de la acción? Pasea tu ratón sobre el vínculo de la acción, y verás en la barra inferior de tu navegador un código JavaScript que incluye el nombre calificado de la acción:
Ahora ya sabes como crear una prueba para probar las operaciones de mantenimiento básicas de un módulo. No es necesario escribir una prueba demasiado exhaustiva al principio. Simplemente prueba las cosas básicas, aquellas cosas que normalmente probarías con un navegador. Tu prueba crecerá de forma natural a medida que tu aplicación crezca y los usuarios vayan encontrando fallos.
Aprendamos como ejecutar tu prueba desde Eclipse.
Ejecutar las pruebas desde Eclipse
JUnit está integrado dentro de Eclipse, por eso ejecutar tus prueba en Eclipse es más fácil que quitarle un caramelo a un niño. Pon el ratón sobre tu clase de prueba, y con el botón derecho escoge Run As > JUnit Test:
Si la prueba no es satisfactoria la barra sale roja. Puedes probarlo. Edita PruebaCliente y comenta la línea que da valor al campo nombre:
...
setValue("numero", "77");// setValue("name", "Cliente JUNIT"); // Comenta esta línea
setValue("address.street", "Calle JUNIT");
...
Ahora, reejecuta la prueba. Ya que nombre es una propiedad requerida, un mensaje de error será mostrado al usuario, y el objeto no se grabará:
El assert culpable es assertNoErrors(), el cual además de fallar muestra en la consola los errores mostrados al usuario. Por eso, en la consola de ejecución de tu prueba verás un mensaje como este:
16-jul-2009 18:03 org.openxava.tests.ModuleTestBase assertNoMessages
SEVERE: Error unexpected: Es ogligado que Nombre en Cliente tenga valor
El problema es claro. El cliente no se ha grabado porque el nombre es obligatorio, y éste no se ha especificado.
Has aprendido como se comporta la prueba cuando falla. Ahora, puedes descomentar la línea culpable y volver a ejecutar la prueba para verificar que todo sigue en su sitio.
Crear datos de prueba usando JPA
En tu primera prueba, PruebaCliente, la prueba misma empieza creando los datos que van a ser usados en el resto de la prueba. Este es un buen enfoque, especialmente si quieres probar la entrada de datos también. Pero a veces te interesa probar solo un pequeño caso que falla, o simplemente tu módulo no permite entrada de datos. En cualquier caso puedes crear los datos que necesites para probar usando JPA desde tu prueba.
Los métodos setUp() y tearDown()
Vamos a usar PruebaProducto para aprender como usar JPA para crear datos de prueba. Crearemos algunos productos antes de ejecutar cada prueba y los borraremos después. Veamos el código de PruebaProducto:
packageorg.openxava.facturacion.pruebas;importjava.math.*;importorg.openxava.facturacion.modelo.*;importorg.openxava.tests.*;importstaticorg.openxava.jpa.XPersistence.*;publicclass PruebaProducto extends ModuleTestBase {private Autor autor;// Declaramos las entidades a crearprivate Categoria categoria;// como miembros de instancia para queprivate Producto producto1;// estén disponibles en todos los métodos de pruebaprivate Producto producto2;// y puedan ser borradas al final de cada pruebapublic PruebaProducto(String testName){super(testName, "Facturacion", "Producto");}protectedvoid setUp()throwsException{// setUp() se ejecuta siempre antes de cada pruebasuper.setUp();// Es necesario porque ModuleTestBase lo usa para inicializarse, JPA se inicializa aquí
crearProductos();// Crea los datos usados en las pruebas}protectedvoid tearDown()throwsException{// tearDown() se ejecuta// siempre después de cada pruebasuper.tearDown();// Necesario, ModuleTestBase cierra recursos aquí
borrarProductos();// Se borran los datos usados en las pruebas}publicvoid testBorrarDesdeLista()throwsException{ ... }publicvoid testCambiarPrecio()throwsException{ ... }privatevoid crearProductos(){ ... }privatevoid borrarProductos(){ ... }}
Aquí estamos sobrescribiendo los métodos setUp() y tearDown(). Estos métodos son métodos de JUnit que son ejecutados justo antes y después de ejecutar cada método de prueba. Creamos los datos de prueba antes de ejecutar cada prueba, y borramos los datos después de cada prueba. Así, cada prueba puede contar con unos datos concretos para ejecutarse. No importa si otras pruebas borran o modifican datos, o el orden de ejecución de las pruebas. Siempre, al principio de cada método de prueba tenemos todos los datos listos para usar.
Crear datos con JPA
El método crearProductos() es el responsable de crear los datos de prueba usando JPA. Examinémoslo:
privatevoid crearProductos(){// Crear objetos Java
autor = new Autor();// Se crean objetos de Java convencionales
autor.setNombre("JUNIT Author");// Usamos setters como se suele hacer con Java
categoria = new Categoria();
categoria.setDescripcion("Categoria JUNIT");
producto1 = new Producto();
producto1.setNumero(900000001);
producto1.setDescripcion("Producto JUNIT 1");
producto1.setAutor(autor);
producto1.setCategoria(categoria);
producto1.setPrecio(newBigDecimal("10"));
producto2 = new Producto();
producto2.setNumero(900000002);
producto2.setDescripcion("Producto JUNIT 2");
producto2.setAutor(autor);
producto2.setCategoria(categoria);
producto2.setPrecio(newBigDecimal("20"));// Marcar los objetos como persistentes
getManager().persist(autor);// getManager() es de XPersistence
getManager().persist(categoria);// persist() marca el objeto como persistente
getManager().persist(producto1);// para que se grabe en la base de datos
getManager().persist(producto2);// Confirma los cambios en la base de datos
commit();// commit() es de XPersistence. Graba todos los objetos en la base de datos// y confirma la transacción}
Como puedes ver, primero creas los objetos al estilo convencional de Java. Fíjate que los asignamos a miembros de instancia, así puedes usarlos dentro de la prueba. Entonces, los marcas como persistentes, usando el método persist() del EntityManager de JPA. Para obtener el PersistenceManager solo has de escribir getManager() porque tienes un import estático arriba:
importstaticorg.openxava.jpa.XPersistence.*;
...
getManager().persist(autor);// Gracias al static import de XPersistence es lo mismo que
XPersistence.getManager().persist(autor);
...
commit();// Gracias al static import de XPersistence es lo mismo que
XPersistence.commit();
Para finalizar, commit() (también de XPersistence) graba todos los objetos a la base de datos y entonces confirma la transacción. Después de eso, los datos ya están en la base de datos listos para ser usados por tu prueba.
Borrar datos con JPA
Después de que se ejecute la prueba borraremos los datos de prueba para dejar la base de datos limpia. Esto se hace en el método borrarProductos():
privatevoid borrarProductos(){// Llamado desde tearDown()// por tanto ejecutado después de cada prueba
borrar(producto1, producto2, autor, categoria);// borrar() borra
commit();// Confirma los cambios en la base de datos, en este caso borrando datos}privatevoid borrar(Object ... entidades){// Usamos argumentos varargsfor(Object entidad : entidades){// Iteramos por todos los argumentos
getManager().remove(getManager().merge(entidad));// Borrar(1)}}
Es un simple bucle por todas las entidades usadas en la prueba, borrándolas. Para borrar una entidad con JPA has de usar el método remove(), aunque en este caso has de usar el método merge() también (1). Esto es porque no puedes borrar una entidad desasociada (detached entity). Al usar commit() en crearProductos() todas la entidades grabadas pasaron a ser entidades desasociadas, porque continúan siendo objetos Java válidos pero el contexto
persistente (persistent context, la unión entre las entidades y la base de datos) se perdió en el commit(), por eso tienes que reasociarlas al nuevo contexto persistente. Este concepto es fácil de entender con el siguiente código:
getManager().persist(autor);// autor está asociado al contexto persistente actual
commit();// El contexto persistente actual se termina, y autor pasa a estar desasociado
getManager().remove(autor);// Falla porque autor está desasociado
autor = getManager().merge(autor);// Reasocia autor al contexto actual
getManager().remove(autor);// Funciona
A parte de este curioso detalle sobre el merge(), el código para borrar es bastante sencillo.
Filtrar datos desde modo lista en una prueba
Ahora que ya sabes como crear y borrar datos para las pruebas, examinemos los métodos de prueba para tu módulo Producto. El primero es testBorrarDesdeLista() que selecciona una fila en el modo lista y pulsa en el botón “Borrar seleccionados”. Veamos su código:
publicvoid testBorrarDesdeLista()throwsException{
login("admin", "admin");
setConditionValues("", "JUNIT");// Establece los valores para filtrar los datos
setConditionComparators("=", "contains_comparator");// Pone los comparadores para filtrar los datos
execute("List.filter");// Pulsa el botón para filtrar
assertListRowCount(2);// Verifica que hay 2 filas
checkRow(1);// Seleccionamos la fila 1 (que resulta ser la segunda)
execute("CRUD.deleteSelected");// Pulsa en el botón para borrar
assertListRowCount(1);// Verifica que ahora solo hay una fila}
Aquí filtramos en modo lista todos los productos que contienen la palabra “JUNIT” (recuerda que has creado dos de estos en el método crearProductos()), entonces verificamos que hay dos filas, seleccionamos el segundo producto y lo borramos, verificando al final que la lista se queda con un solo producto.
Has aprendido como seleccionar una fila (usando checkRow()) y como verificar el número de filas (usando assertListRowCount()). Quizás la parte más intrincada es usar setConditionValues() y setConditionComparators(). Ambos métodos reciben una cantidad variable de cadenas con valores y comparadores para la condición, tal como muestra aquí:
Los valores son asignados al filtro de la lista secuencialmente (de izquierda a derecha). En este caso hay dos valores, pero puedes usar todos los que necesites. No es necesario que especifiques todos los valores. El método setConditionValues() admite cualquier cadena mientras que setConditionComparators() admite los siguientes valores posibles: starts_comparator, contains_comparator, =, <>, >=, <=, >,<, in_comparator, not_in_comparator y range_comparator.
Usar instancias de entidad dentro de una prueba
La prueba que queda, testCambiarPrecio(), simplemente escoge un producto y cambia su precio. Vamos a usar en él una entidad creada en crearProductos():
Lo único nuevo en esta prueba es que para dar valor al número usado para buscar el producto, lo obtenemos de producto1.getNumero() (1). Recuerda que producto1 es una variable de instancia de la prueba a la que se asigna valor en crearProductos(), el cual es llamado desde setUp(), es decir se ejecuta antes de cada prueba.
Ya tienes la prueba para Producto y al mismo tiempo has aprendido como probar usando datos de prueba creados mediante JPA. Ejecútalo, debería salir verde.
Usar datos ya existentes para probar
A veces puedes simplificar la prueba usando una base de datos que contenga los datos necesarios para la prueba. Si no quieres probar la creación de datos desde el módulo, y no borras datos en la prueba, ésta puede ser una buena opción.
Por ejemplo, puedes probar Autor y Categoria con una prueba tan simple como esta:
packageorg.openxava.facturacion.pruebas;importorg.openxava.tests.*;publicclass PruebaAutor extends ModuleTestBase {public PruebaAutor(String nombrePrueba){super(nombrePrueba, "Facturacion", "Autor");}publicvoid testReadAuthor()throwsException{
login("admin", "admin");
assertValueInList(0, 0, "JAVIER CORCOBADO");// El primer autor en la// lista es JAVIER CORCOBADO
execute("Mode.detailAndFirst");// Al cambiar a modo detalle// se visualiza el primero objeto de la lista
assertValue("nombre", "JAVIER CORCOBADO");
assertCollectionRowCount("productos", 2);// Tiene 2 productos
assertValueInCollection("productos", 0, // Fila 0 de productos"numero", "2");// tiene “2” en la columna “numero”
assertValueInCollection("productos", 0, "descripcion", "Arco iris de lágrimas");
assertValueInCollection("productos", 1, "numero", "3");
assertValueInCollection("productos", 1, "descripcion", "Ritmo de sangre");}}
Esta prueba verifica que el primer autor en la lista es “JAVIER CORCOBADO”, recuerda crearlo antes de ejecutar la prueba. Entonces va al detalle y confirma que tiene una colección llamada products con 2 productos: “Arco iris de lágrimas” y “Ritmo de sangre”, antes de ejecutar la prueba créalos y asocialos a "JAVIER CORCOBADO". De paso, has aprendido como usar los métodos assertValueInList(), assertValueInCollection() y assertCollectionRowCount().
Podemos usar la misma técnica para probar el módulo Categoria:
En este caso solo verificamos que en la lista las tres primeras categorías son “MÚSICA”, “LIBROS” y “SOFTWARE”. Acuerdate de crearlos antes de ejecutar esta prueba.
Puedes ver como la técnica de usar datos preexistentes de una base de datos de prueba te permite crear pruebas más simples. Empezar con una prueba simple e ir complicándolo bajo demanda es una buena idea. Recuerda añadir los datos correspondientes usando los módulos antes de ejecutar estas pruebas.
Probar colecciones
Es el momento de enfrentarnos a la prueba del módulo principal de tu aplicación, PruebaFactura. Por ahora la funcionalidad del módulo Factura es limitada, solo puedes añadir, borrar y modificar facturas. Aun así, esta es la prueba más extensa; además contiene una colección, por tanto aprenderás como probar las colecciones.
Dividir la prueba en varios métodos
La prueba para crear una factura está dividida en varios métodos:
packageorg.openxava.facturacion.pruebas;importjava.text.*;importjava.util.*;importjavax.persistence.*;importorg.openxava.tests.*;importorg.openxava.util.*;importstaticorg.openxava.jpa.XPersistence.*;// Para usar JPApublicclass PruebaFactura extends ModuleTestBase {privateString numero;// Para almacenar el número de la factura que probamospublic PruebaFactura(String nombrePrueba){super(nombrePrueba, "Facturacion", "Factura");}publicvoid testCrear()throwsException{// El método de prueba
login("admin", "admin");
verificarValoresDefecto();
escogerCliente();
anyadirDetalles();
ponerOtrasPropiedades();
grabar();
verificarCreado();
borrar();}privatevoid verificarValoresDefecto()throwsException{ … }privatevoid escogerCliente()throwsException{ … }privatevoid anyadirDetalles()throwsException{ … }privatevoid ponerOtrasPropiedades()throwsException{ … }privatevoid grabar()throwsException{ … }privatevoid verificarCreado()throwsException{ … }privatevoid borrar()throwsException{ … }privateString getAnyoActual(){ … }privateString getFechaActual(){ … }privateString getNumero(){ … }}
El único método de prueba de esta clase es testCrear(), pero dado que es bastante extenso, es mejor dividirlo en varios métodos más pequeños. De hecho, es una buena práctica de orientación a objetos escribir métodos cortos.
Ya que el método es corto puedes ver con un solo golpe de vista que es lo que hace. En este caso verifica los valores por defecto para una factura nueva, escoge un cliente, añade las líneas de detalle, añade otras propiedades, graba la factura, verifica que ha sido guardada correctamente y al final la borra. Entremos en los detalles de cada uno de estos pasos.
Verificar valores por defecto
Lo primero es verificar que los valores por defecto para una factura nueva son calculados correctamente. Esto se hace en el método verificarValoresDefecto().
Cuando el usuario pulsa en “Nuevo”, los campos año, número y fecha tienen que rellenarse con datos válidos. El método verificarValoresDefecto() precisamente comprueba esto. Usa varios métodos de utilidad para calcular los valores esperados:
privateString getAnyoActual(){// Año actual en formato cadenareturnnewSimpleDateFormat("yyyy").format(newDate());// La forma// típica de hacerlo con Java}privateString getFechaActual(){// Fecha actual como una cadenareturnnewSimpleDateFormat("dd/MM/yyyy").format(newDate());// La forma// típica de hacerlo con Java}privateString getNumero(){// El número de factura para una factura nuevaif(numero == null){// Usamos inicialización vagaQuery query = getManager(). // Una consulta JPA para obtener el último número
createQuery("select max(f.numero) from Factura f where f.anyo = :anyo");
query.setParameter("anyo", Dates.getYear(newDate()));// Dates es una// utilidad de OpenXavaInteger ultimoNumero = (Integer) query.getSingleResult();if(ultimoNumero == null) ultimoNumero = 0;
numero = Integer.toString(ultimoNumero + 1);// Añadimos 1 al// último número de factura}return numero;}
Los métodos The getAnyoActual() y getFechaActual() usan técnicas clásicas de Java para formatear la fecha como una cadena.
El método getNumero() es un poco más complejo: usa JPA para calcular el último número de factura del año en curso y después devuelve este valor más uno. Dado que acceder a la base de datos es más pesado que un simple cálculo Java, usamos una inicialización vaga. Una inicialización vaga retrasa el cálculo hasta la primera vez que se necesita, y después lo almacena para futuros usos. Esto lo hacemos guardando el valor en el campo numero.
Fíjate en el uso de la clase Dates para extraer el año de la fecha. Dates es una clase de utilidad que puedes encontrar en org.openxava.util.
Entrada de datos
Ahora es el momento de escogerCliente() de la factura:
privatevoid escogerCliente()throwsException{
setValue("cliente.numero", "1");
assertValue("cliente.nombre", "JAVIER PANIZA");// El cliente 1 debe de existir en la DB}
Al introducir el número de cliente el nombre del cliente se rellena con un valor apropiado. La prueba confía en que el cliente 1 con nombre "JAVIER PANIZA" existe, deberías crearlo antes de ejecutar la prueba. Ya hemos asociamos el cliente 1 con la factura actual.
Y ahora viene la parte más peliaguda de la prueba: añadir las líneas de detalle:
privatevoid anyadirDetalles()throwsException{
assertCollectionRowCount("detalles", 0);// La colección esta vacía// Añadir una línea de detalle
setValueInCollection("detalles", 0, // 0 es la primera fila"producto.numero", "1");
assertValueInCollection("detalles", 0,
"producto.descripcion", "Peopleware: Productive Projects and Teams");
setValueInCollection("detalles", 0, "cantidad", "2");// Añadir otro detalle
setValueInCollection("detalles", 1, "producto.numero", "2");
assertValueInCollection("detalles", 1, "producto.descripcion", "Arco iris de lágrimas");
setValueInCollection("detalles", 1, "cantidad", "1");
assertCollectionRowCount("detalles", 2);// Ahora tenemos 2 filas}
Probar una colección es exactamente igual que probar cualquier otra parte de tu aplicación, solo has de seguir los mismos pasos que un usuario haría con el navegador. Tienes métodos como setValueInCollection(), assertValueInCollection() o assertCollectionRowCount() para trabajar con colecciones. Nota que estos métodos tienen el nombre de la colección como primer argumento y algunos reciben el número de fila siendo el 0 la primera fila. Recuerda añadir a la base de datos los productos 1 y 2 con sus correspondientes descripciones antes de ejecutar esta prueba.
Ahora que tenemos los detalles añadidos, vamos a llenar los datos restantes y grabar la factura. Los datos restantes se establecen en el método ponerOtrasPropiedades():
privatevoid ponerOtrasPropiedades()throwsException{
setValue("observaciones", "Esto es una prueba JUNIT");}
Aquí ponemos valor al campo observaciones. Y ahora estamos listos para grabar la factura:
Simplemente pulsa en “Grabar”, entonces verifica que no ha habido errores y la vista se ha limpiado.
Verificar los datos
Ahora, buscamos la factura recién creada para verificar que ha sido grabada correctamente. Esto se hace en el método verificarCreado():
privatevoid verificarCreado()throwsException{
execute("CRUD.search");// Saca el diálogo para buscar
setValue("anyo", getAnyoActual());// El año actual en el campo año
setValue("numero", getNumero());// El número de la factura usada en la prueba
execute("Search.search");// Carga la factura desde la base de datos// En el resto de la prueba confirmamos que los valores son los correctos
assertValue("anyo", getAnyoActual());
assertValue("numero", getNumero());
assertValue("fecha", getFechaActual());
assertValue("cliente.numero", "1");
assertValue("cliente.nombre", "JAVIER PANIZA");
assertCollectionRowCount("detalles", 2);// Fila 0
assertValueInCollection("detalles", 0, "producto.numero", "1");
assertValueInCollection("detalles", 0, "producto.descripcion",
"Peopleware: Productive Projects and Teams");
assertValueInCollection("detalles", 0, "cantidad", "2");// Fila 1
assertValueInCollection("detalles", 1, "producto.numero", "2");
assertValueInCollection("detalles", 1, "producto.descripcion",
"Arco iris de lágrimas");
assertValueInCollection("detalles", 1, "cantidad", "1");
assertValue("observaciones", "Esto es una prueba JUNIT");}
Después de buscar la factura creada verificamos que los valores que hemos grabado están ahí. Si la prueba llega a este punto tu módulo Factura funciona bien. Solo nos queda borrar la factura creada para que la prueba se pueda ejecutar la siguiente vez. Hacemos esto en el método borrar():
Simplemente presiona en “Borrar” y verifica que no se han producido errores.
¡Enhorabuena! Has completado tu PruebaFactura. Ya puedes ejecutarla, debería salir verde, si no comprueba que los datos en la base de datos están bien, puede que tengas que añadir los productos, cliente, etc. correspondientes.
Suite
Tienes 5 casos de prueba que velan por tu código, preservando la calidad de tu aplicación. Cuando termines alguna mejora o corrección en tu aplicación ejecuta todas tus pruebas unitarias para verificar que la funcionalidad existente no se ha roto.
Tradicionalmente, para ejecutar todos las pruebas de tu aplicación deberías crear una suite de pruebas, y ejecutarla. Una suite de pruebas es una clase que agrega todas tus pruebas JUnit para que puedas ejecutarlas todas de un golpe. Afortunadamente, si trabajas con Eclipse no necesitas escribir una clase de suite, Eclipse te permite ejecutar todas las pruebas de tu aplicación automáticamente:
Es decir, si ejecutas Run As > JUnit Test en el proyecto, se ejecutarán todas sus pruebas JUnit.
Resumen
Has automatizado las pruebas de toda la funcionalidad actual de tu aplicación. Puede parecer que este código de prueba es mucho más largo y aburrido que el código real de la aplicación. Pero recuerda, el código de prueba es el tesoro más valioso que tienes. Quizás ahora no me creas, pero trata de hacer pruebas JUnit y una vez te hayan salvado la vida, ya no podrás desarrollar sin pruebas automáticas nunca más.
¿Qué probar? No hagas pruebas exhaustivas al principio. Es mejor probar poco que no probar nada. Si tratas de hacer pruebas muy exhaustivas acabarás no haciendo pruebas en absoluto. Empieza haciendo algunas pruebas JUnit para tu código, y con cada nueva característica o nuevo arreglo añade nuevas pruebas. Al final, tendrás una suite de pruebas muy completa. En resumen, prueba poco, pero prueba siempre.
Sí, hacer pruebas automáticas es una tarea continua. Y para predicar con el ejemplo a partir de ahora escribiremos todas las pruebas para el código que desarrollemos en el resto del libro. De esta manera aprenderás más trucos sobre las pruebas JUnit en las siguientes lecciones.
Table of Contents
Lección 3: Pruebas automáticas
Las pruebas son la parte más importante del desarrollo de software. No importa cuan bonita, rápida o tecnológicamente avanzada sea tu aplicación, si falla, darás una impresión muy pobre.Hacer pruebas manuales, es decir, abrir el navegador y ejecutar la aplicación exactamente como lo haría un usuario final, no es viable; porque el problema real no está en el código que acabas de escribir, sino en el código que ya estaba ahí. Normalmente pruebas el código que acabas de escribir, pero no pruebas todo el código que ya existe en tu aplicación. Y sabes muy bien que cuando tocas cualquier parte de tu aplicación puedes romper cualquier otra parte inadvertidamente.
Necesitas poder hacer cualquier cambio en tu código con la tranquilidad de que no vas a romper tu aplicación. Una forma de conseguirlo, es usando pruebas automáticas. Vamos a hacer pruebas automáticas usando JUnit.
JUnit
JUnit es una herramienta muy popular para hacer pruebas automáticas. Esta herramienta está integrada con Eclipse, por tanto no necesitas descargarla para poder usarla. OpenXava extiende las capacidades de JUnit para permitir probar un módulo de OpenXava exactamente de la misma forma que lo haría un usuario final. De hecho, OpenXava usa HtmlUnit, un software que simula un navegador real (incluyendo JavaScript) desde Java. Todo está disponible desde la clase de OpenXava ModuleTestBase, que te permite automatizar las pruebas que tú harías a mano usando un navegador de verdad de una forma simple.La mejor manera de entender como funcionan las pruebas en OpenXava es verlo en acción.
ModuleTestBase para probar módulos
Para crear una prueba para un módulo de OpenXava extendemos de la clase ModuleTestBase del paquete org.openxava.tests. Esta clase te permite conectar con un módulo OpenXava como un navegador real, y tiene muchos métodos útiles para probar tu módulo. Creemos la prueba para tu módulo Cliente.El código para la prueba
Crea un paquete nuevo llamado org.openxava.facturacion.pruebas y dentro de él una nueva clase llamada PruebaCliente con el siguiente código:Esta prueba crea un nuevo cliente, lo busca, lo modifica y al final lo borra. Aquí ves como puedes usar métodos como execute() o setValue() para simular las acciones del usuario, y métodos como assertValue(), assertNoErrors() o assertMessage() para verificar el estado de la interfaz de usuario. Tu prueba actúa como las manos y los ojos del usuario:
En execute() tienes que especificar el nombre calificado de la acción, esto quiere decir NombreControlador.nombreAccion. ¿Cómo puede saber el nombre de la acción? Pasea tu ratón sobre el vínculo de la acción, y verás en la barra inferior de tu navegador un código JavaScript que incluye el nombre calificado de la acción:
Ahora ya sabes como crear una prueba para probar las operaciones de mantenimiento básicas de un módulo. No es necesario escribir una prueba demasiado exhaustiva al principio. Simplemente prueba las cosas básicas, aquellas cosas que normalmente probarías con un navegador. Tu prueba crecerá de forma natural a medida que tu aplicación crezca y los usuarios vayan encontrando fallos.
Aprendamos como ejecutar tu prueba desde Eclipse.
Ejecutar las pruebas desde Eclipse
JUnit está integrado dentro de Eclipse, por eso ejecutar tus prueba en Eclipse es más fácil que quitarle un caramelo a un niño. Pon el ratón sobre tu clase de prueba, y con el botón derecho escoge Run As > JUnit Test:Si la prueba no es satisfactoria la barra sale roja. Puedes probarlo. Edita PruebaCliente y comenta la línea que da valor al campo nombre:
Ahora, reejecuta la prueba. Ya que nombre es una propiedad requerida, un mensaje de error será mostrado al usuario, y el objeto no se grabará:
El assert culpable es assertNoErrors(), el cual además de fallar muestra en la consola los errores mostrados al usuario. Por eso, en la consola de ejecución de tu prueba verás un mensaje como este:
El problema es claro. El cliente no se ha grabado porque el nombre es obligatorio, y éste no se ha especificado.
Has aprendido como se comporta la prueba cuando falla. Ahora, puedes descomentar la línea culpable y volver a ejecutar la prueba para verificar que todo sigue en su sitio.
Crear datos de prueba usando JPA
En tu primera prueba, PruebaCliente, la prueba misma empieza creando los datos que van a ser usados en el resto de la prueba. Este es un buen enfoque, especialmente si quieres probar la entrada de datos también. Pero a veces te interesa probar solo un pequeño caso que falla, o simplemente tu módulo no permite entrada de datos. En cualquier caso puedes crear los datos que necesites para probar usando JPA desde tu prueba.Los métodos setUp() y tearDown()
Vamos a usar PruebaProducto para aprender como usar JPA para crear datos de prueba. Crearemos algunos productos antes de ejecutar cada prueba y los borraremos después. Veamos el código de PruebaProducto:Aquí estamos sobrescribiendo los métodos setUp() y tearDown(). Estos métodos son métodos de JUnit que son ejecutados justo antes y después de ejecutar cada método de prueba. Creamos los datos de prueba antes de ejecutar cada prueba, y borramos los datos después de cada prueba. Así, cada prueba puede contar con unos datos concretos para ejecutarse. No importa si otras pruebas borran o modifican datos, o el orden de ejecución de las pruebas. Siempre, al principio de cada método de prueba tenemos todos los datos listos para usar.
Crear datos con JPA
El método crearProductos() es el responsable de crear los datos de prueba usando JPA. Examinémoslo:Como puedes ver, primero creas los objetos al estilo convencional de Java. Fíjate que los asignamos a miembros de instancia, así puedes usarlos dentro de la prueba. Entonces, los marcas como persistentes, usando el método persist() del EntityManager de JPA. Para obtener el PersistenceManager solo has de escribir getManager() porque tienes un import estático arriba:
Para finalizar, commit() (también de XPersistence) graba todos los objetos a la base de datos y entonces confirma la transacción. Después de eso, los datos ya están en la base de datos listos para ser usados por tu prueba.
Borrar datos con JPA
Después de que se ejecute la prueba borraremos los datos de prueba para dejar la base de datos limpia. Esto se hace en el método borrarProductos():Es un simple bucle por todas las entidades usadas en la prueba, borrándolas. Para borrar una entidad con JPA has de usar el método remove(), aunque en este caso has de usar el método merge() también (1). Esto es porque no puedes borrar una entidad desasociada (detached entity). Al usar commit() en crearProductos() todas la entidades grabadas pasaron a ser entidades desasociadas, porque continúan siendo objetos Java válidos pero el contexto
persistente (persistent context, la unión entre las entidades y la base de datos) se perdió en el commit(), por eso tienes que reasociarlas al nuevo contexto persistente. Este concepto es fácil de entender con el siguiente código:
A parte de este curioso detalle sobre el merge(), el código para borrar es bastante sencillo.
Filtrar datos desde modo lista en una prueba
Ahora que ya sabes como crear y borrar datos para las pruebas, examinemos los métodos de prueba para tu módulo Producto. El primero es testBorrarDesdeLista() que selecciona una fila en el modo lista y pulsa en el botón “Borrar seleccionados”. Veamos su código:Aquí filtramos en modo lista todos los productos que contienen la palabra “JUNIT” (recuerda que has creado dos de estos en el método crearProductos()), entonces verificamos que hay dos filas, seleccionamos el segundo producto y lo borramos, verificando al final que la lista se queda con un solo producto.
Has aprendido como seleccionar una fila (usando checkRow()) y como verificar el número de filas (usando assertListRowCount()). Quizás la parte más intrincada es usar setConditionValues() y setConditionComparators(). Ambos métodos reciben una cantidad variable de cadenas con valores y comparadores para la condición, tal como muestra aquí:
Los valores son asignados al filtro de la lista secuencialmente (de izquierda a derecha). En este caso hay dos valores, pero puedes usar todos los que necesites. No es necesario que especifiques todos los valores. El método setConditionValues() admite cualquier cadena mientras que setConditionComparators() admite los siguientes valores posibles: starts_comparator, contains_comparator, =, <>, >=, <=, >, <, in_comparator, not_in_comparator y range_comparator.
Usar instancias de entidad dentro de una prueba
La prueba que queda, testCambiarPrecio(), simplemente escoge un producto y cambia su precio. Vamos a usar en él una entidad creada en crearProductos():Lo único nuevo en esta prueba es que para dar valor al número usado para buscar el producto, lo obtenemos de producto1.getNumero() (1). Recuerda que producto1 es una variable de instancia de la prueba a la que se asigna valor en crearProductos(), el cual es llamado desde setUp(), es decir se ejecuta antes de cada prueba.
Ya tienes la prueba para Producto y al mismo tiempo has aprendido como probar usando datos de prueba creados mediante JPA. Ejecútalo, debería salir verde.
Usar datos ya existentes para probar
A veces puedes simplificar la prueba usando una base de datos que contenga los datos necesarios para la prueba. Si no quieres probar la creación de datos desde el módulo, y no borras datos en la prueba, ésta puede ser una buena opción.Por ejemplo, puedes probar Autor y Categoria con una prueba tan simple como esta:
Esta prueba verifica que el primer autor en la lista es “JAVIER CORCOBADO”, recuerda crearlo antes de ejecutar la prueba. Entonces va al detalle y confirma que tiene una colección llamada products con 2 productos: “Arco iris de lágrimas” y “Ritmo de sangre”, antes de ejecutar la prueba créalos y asocialos a "JAVIER CORCOBADO". De paso, has aprendido como usar los métodos assertValueInList(), assertValueInCollection() y assertCollectionRowCount().
Podemos usar la misma técnica para probar el módulo Categoria:
En este caso solo verificamos que en la lista las tres primeras categorías son “MÚSICA”, “LIBROS” y “SOFTWARE”. Acuerdate de crearlos antes de ejecutar esta prueba.
Puedes ver como la técnica de usar datos preexistentes de una base de datos de prueba te permite crear pruebas más simples. Empezar con una prueba simple e ir complicándolo bajo demanda es una buena idea. Recuerda añadir los datos correspondientes usando los módulos antes de ejecutar estas pruebas.
Probar colecciones
Es el momento de enfrentarnos a la prueba del módulo principal de tu aplicación, PruebaFactura. Por ahora la funcionalidad del módulo Factura es limitada, solo puedes añadir, borrar y modificar facturas. Aun así, esta es la prueba más extensa; además contiene una colección, por tanto aprenderás como probar las colecciones.Dividir la prueba en varios métodos
La prueba para crear una factura está dividida en varios métodos:El único método de prueba de esta clase es testCrear(), pero dado que es bastante extenso, es mejor dividirlo en varios métodos más pequeños. De hecho, es una buena práctica de orientación a objetos escribir métodos cortos.
Ya que el método es corto puedes ver con un solo golpe de vista que es lo que hace. En este caso verifica los valores por defecto para una factura nueva, escoge un cliente, añade las líneas de detalle, añade otras propiedades, graba la factura, verifica que ha sido guardada correctamente y al final la borra. Entremos en los detalles de cada uno de estos pasos.
Verificar valores por defecto
Lo primero es verificar que los valores por defecto para una factura nueva son calculados correctamente. Esto se hace en el método verificarValoresDefecto().Cuando el usuario pulsa en “Nuevo”, los campos año, número y fecha tienen que rellenarse con datos válidos. El método verificarValoresDefecto() precisamente comprueba esto. Usa varios métodos de utilidad para calcular los valores esperados:
Los métodos The getAnyoActual() y getFechaActual() usan técnicas clásicas de Java para formatear la fecha como una cadena.
El método getNumero() es un poco más complejo: usa JPA para calcular el último número de factura del año en curso y después devuelve este valor más uno. Dado que acceder a la base de datos es más pesado que un simple cálculo Java, usamos una inicialización vaga. Una inicialización vaga retrasa el cálculo hasta la primera vez que se necesita, y después lo almacena para futuros usos. Esto lo hacemos guardando el valor en el campo numero.
Fíjate en el uso de la clase Dates para extraer el año de la fecha. Dates es una clase de utilidad que puedes encontrar en org.openxava.util.
Entrada de datos
Ahora es el momento de escogerCliente() de la factura:Al introducir el número de cliente el nombre del cliente se rellena con un valor apropiado. La prueba confía en que el cliente 1 con nombre "JAVIER PANIZA" existe, deberías crearlo antes de ejecutar la prueba. Ya hemos asociamos el cliente 1 con la factura actual.
Y ahora viene la parte más peliaguda de la prueba: añadir las líneas de detalle:
Probar una colección es exactamente igual que probar cualquier otra parte de tu aplicación, solo has de seguir los mismos pasos que un usuario haría con el navegador. Tienes métodos como setValueInCollection(), assertValueInCollection() o assertCollectionRowCount() para trabajar con colecciones. Nota que estos métodos tienen el nombre de la colección como primer argumento y algunos reciben el número de fila siendo el 0 la primera fila. Recuerda añadir a la base de datos los productos 1 y 2 con sus correspondientes descripciones antes de ejecutar esta prueba.
Ahora que tenemos los detalles añadidos, vamos a llenar los datos restantes y grabar la factura. Los datos restantes se establecen en el método ponerOtrasPropiedades():
Aquí ponemos valor al campo observaciones. Y ahora estamos listos para grabar la factura:
Simplemente pulsa en “Grabar”, entonces verifica que no ha habido errores y la vista se ha limpiado.
Verificar los datos
Ahora, buscamos la factura recién creada para verificar que ha sido grabada correctamente. Esto se hace en el método verificarCreado():Después de buscar la factura creada verificamos que los valores que hemos grabado están ahí. Si la prueba llega a este punto tu módulo Factura funciona bien. Solo nos queda borrar la factura creada para que la prueba se pueda ejecutar la siguiente vez. Hacemos esto en el método borrar():
Simplemente presiona en “Borrar” y verifica que no se han producido errores.
¡Enhorabuena! Has completado tu PruebaFactura. Ya puedes ejecutarla, debería salir verde, si no comprueba que los datos en la base de datos están bien, puede que tengas que añadir los productos, cliente, etc. correspondientes.
Suite
Tienes 5 casos de prueba que velan por tu código, preservando la calidad de tu aplicación. Cuando termines alguna mejora o corrección en tu aplicación ejecuta todas tus pruebas unitarias para verificar que la funcionalidad existente no se ha roto.Tradicionalmente, para ejecutar todos las pruebas de tu aplicación deberías crear una suite de pruebas, y ejecutarla. Una suite de pruebas es una clase que agrega todas tus pruebas JUnit para que puedas ejecutarlas todas de un golpe. Afortunadamente, si trabajas con Eclipse no necesitas escribir una clase de suite, Eclipse te permite ejecutar todas las pruebas de tu aplicación automáticamente:
Es decir, si ejecutas Run As > JUnit Test en el proyecto, se ejecutarán todas sus pruebas JUnit.
Resumen
Has automatizado las pruebas de toda la funcionalidad actual de tu aplicación. Puede parecer que este código de prueba es mucho más largo y aburrido que el código real de la aplicación. Pero recuerda, el código de prueba es el tesoro más valioso que tienes. Quizás ahora no me creas, pero trata de hacer pruebas JUnit y una vez te hayan salvado la vida, ya no podrás desarrollar sin pruebas automáticas nunca más.¿Qué probar? No hagas pruebas exhaustivas al principio. Es mejor probar poco que no probar nada. Si tratas de hacer pruebas muy exhaustivas acabarás no haciendo pruebas en absoluto. Empieza haciendo algunas pruebas JUnit para tu código, y con cada nueva característica o nuevo arreglo añade nuevas pruebas. Al final, tendrás una suite de pruebas muy completa. En resumen, prueba poco, pero prueba siempre.
Sí, hacer pruebas automáticas es una tarea continua. Y para predicar con el ejemplo a partir de ahora escribiremos todas las pruebas para el código que desarrollemos en el resto del libro. De esta manera aprenderás más trucos sobre las pruebas JUnit en las siguientes lecciones.
Descargar código fuente de esta lección
¿Problemas con la lección? Pregunta en el foro ¿Ha ido bien? Ve a la lección 4